package ufpb.compiladores.sintatico;

import java.io.File;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.LinkedList;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

/**
 *
 * @author SemiNick
 */
public final class XMLLoader {
    
    private final File conf;
    private Production initial = null;
    
    public XMLLoader(File f) throws FileNotFoundException {
        
        if (f == null) {
            
            throw new NullPointerException("File cannot be null");
        } else if (!f.exists()) {
            
            throw new FileNotFoundException("Configuration file does not exist");
        }
        
        this.conf = f;
    }
    
    public Enviroment createEnviroment() throws Exception {
        
        Enviroment env = new SyntacticalAnalyzer();
        Document doc = DocumentBuilderFactory.newInstance().newDocumentBuilder()
                .parse(this.conf);
        
        Node rootNode = doc.getFirstChild();
        if (rootNode.getNodeName().equals("Syntactical")) {
            
            NodeList childs = rootNode.getChildNodes();
            final int len = childs.getLength();
            
            for (int i = 0; i < len; i++) {
                
                Node an = childs.item(i);
                String name = an.getNodeName();
                if (name.equals("Production")) {
                 
                    processProductionNode(an, env);
                }
            }
        }
        
        if (this.initial != null) {
            
            env.setInitialProduction(initial);
        } else  {
        
            throw new Exception("Missing initial production");
        }
        
        verifyIntegrity(env);
        return env;
    }
    
    private void processProductionNode(Node pn, Enviroment env) throws Exception {
        
        Node nini = pn.getAttributes().getNamedItem("initial");
        boolean opcional = false;
        Node po = pn.getAttributes().getNamedItem("opcional");
        if (po != null) {
            
            opcional = po.getTextContent().equalsIgnoreCase("true");
        }
        boolean ini = false;
        if (nini != null) {
            
            ini = nini.getTextContent().equals("true");
        }
        
        Node nname = pn.getAttributes().getNamedItem("name");
        if (nname == null) {
         
            throw new Exception("Missing name attibute in Production definition");
        }
        
        String name = nname.getTextContent();
        
        Production p = new Production(name, opcional);
        
        NodeList nl = pn.getChildNodes();
        final int nli = nl.getLength();
        for (int i = 0; i < nli; i++) {
            
            Node gn = nl.item(i);
            String gnname = gn.getNodeName();
            if (gnname.equals("Clause")) {
                
                processClauseNode(gn, p);
            }
        }
        
        if (ini) {
            
            this.initial = p;
        }
        env.addProduction(p);
    }
    
    private void processClauseNode(Node cn, Production p) throws Exception {
        
        boolean opt = false;
        Node optn = cn.getAttributes().getNamedItem("opcional");
        if (optn != null) {
            
            opt = optn.getTextContent().equalsIgnoreCase("true");
        }
        NodeList nl = cn.getChildNodes();
        final int nll = nl.getLength();
        Clause c = new Clause(opt);
        int ne = 0;
        
        for (int i = 0; i < nll; i++) {
            
            Node an = nl.item(i);
            if (an.getNodeName().equals("Entity")) {

                Node ntype = an.getAttributes().getNamedItem("type");
                Node nvalue = an.getAttributes().getNamedItem("value");
                if (ntype == null || (nvalue == null && !ntype.getTextContent().equals("void"))) {
                    
                    throw new Exception("Missing type and/or value in Entity definition");
                }
                
                String type = ntype.getTextContent();
                String value = nvalue.getTextContent();
                Entity.EntityType et;
                if (type.equals("token")) {
                    
                    et = Entity.EntityType.TOKEN;
                } else if (type.equals("type")) {
                    
                    et = Entity.EntityType.TYPE;
                } else if (type.equals("production")) {
                
                    et = Entity.EntityType.PRODUCTION;
                } else if (type.equals("void")) {
                    
                    et = Entity.EntityType.VOID;
                } else {
                    
                    throw new Exception("Invalid Entity type");
                }
                
                if (et == Entity.EntityType.VOID) {
                    
                    c.addEntity(new Entity(et, ""));
                    ne++;
                } else {
                    
                    c.addEntity(new Entity(et, value));
                    ne++;
                }
            }
        }
        
        if (ne > 0) {
        
            p.addClause(c);
        }
    }
    
    private void verifyIntegrity(Enviroment env) throws Exception {
        
        for (Production p : env.getAllProductions()) {
            
            for (Clause c : p.getClauses()) {
                
                for (Entity e : c.getEntities()) {
                    
                    if (e.getType() == Entity.EntityType.PRODUCTION) {
                        
                        if (env.getProduction(e.value()) == null) {
                            
                            throw new Exception("Entity: " + e.value() + " in "
                                    + " Production: " + p.getName()
                                    + " uses a invalid production");
                        }
                    }
                }
            }
        }
    }
    
    private static class SyntacticalAnalyzer extends Enviroment {

        @Override
        public void removeRecursion() {

            Production[] allProductions = null; 
            Clause[] allClauses = null;
            Entity[] allEntities = null;
            boolean temRecursao = false;
            
            allProductions = this.getAllProductions();
            for (int i=0; i<allProductions.length; i++){

                allClauses = allProductions[i].getClauses();

                temRecursao = false;
                for (int j=0; j<allClauses.length; j++){

                    allEntities = allClauses[j].getEntities();

                    if ((allEntities[0].getType()==(Entity.EntityType.PRODUCTION))
                            && (allEntities[0].value().equals((allProductions[i].getName())))) {

                    temRecursao = true;
                    break;
                    }
                }

                if (temRecursao){

                    Production pLinha = new Production(allProductions[i].getName()+'\'',
                            allProductions[i].isOptional());

                    for (int j=0; j<allClauses.length; j++){

                        allEntities = allClauses[j].getEntities();

                        if ((allEntities[0].getType()==(Entity.EntityType.PRODUCTION))
                            && (allEntities[0].value().equals((allProductions[i].getName())))) {

                                Clause c = new Clause(allClauses[j].isOptional());

                            for (int k=1; k<allEntities.length; k++){

                                c.addEntity(allEntities[k]);
                            }

                            c.addEntity(new Entity(Entity.EntityType.PRODUCTION, pLinha.getName()));
                            pLinha.addClause(c);

                            allProductions[i].removeClause(allClauses[j]);

                        } else {

                            if (allClauses[j].getEntities()[0].getType() == Entity.EntityType.VOID){
                                allProductions[i].removeClause(allClauses[j]);
                            }

                            else {

                                allClauses[j].addEntity(new Entity(Entity.EntityType.PRODUCTION, pLinha.getName()));
                            }

                            }

                    }


                    Clause cl = new Clause(false);
                    cl.addEntity(new Entity(Entity.EntityType.VOID,""));
                    pLinha.addClause(cl);
                    this.addProduction(pLinha);

                    allClauses = allProductions[i].getClauses();

                    if (allClauses.length == 0 ){

                        Clause cl2 = new Clause(false);
                        cl2.addEntity(new Entity(Entity.EntityType.PRODUCTION, pLinha.getName()));
                        allProductions[i].addClause(cl2);
                    }
                }
            }
        }

        @Override
        public void removeNonDeterminism() {

            Production[] allProductions = null;
            Clause[] allClauses = null;
            Entity[] allEntities = null;
            boolean temNaoDet = false;
            Entity entidadeAux = null;
            boolean temVazio = false;

            allProductions = this.getAllProductions();

            for (int i=0; i<allProductions.length; i++){

                allClauses = allProductions[i].getClauses();

                //temNaoDet = false;

                for (int j=0; j<allClauses.length-1; j++){

                    temVazio = false;
                    temNaoDet = false;

                    for(int k=j+1; k<allClauses.length; k++){ //compara o da posição j com todos j+1 até o número de cláusulas

                        if (allClauses[j].getEntities()[0].value().equals(allClauses[k].getEntities()[0].value()) &&
                                (allClauses[j].getEntities()[0].getType() == allClauses[k].getEntities()[0].getType())){

                            entidadeAux = allClauses[j].getEntities()[0]; //guarda a entidade nao deterministica
                            temNaoDet = true;
                            //k++;
                            break;
                        }
                    }

                    if (temNaoDet){

                        Production p2Linhas = new Production(allProductions[i].getName()+'2',
                                allProductions[i].isOptional());

                        for (int m=0; m<allClauses.length; m++){

                            allEntities = allClauses[m].getEntities();

                            if (allEntities[0].value().equals(entidadeAux.value()) &&
                                    (allEntities[0].getType() == entidadeAux.getType())){

                                Clause cl = new Clause(allClauses[j].isOptional());

                                for (int n=1; n<allEntities.length; n++){

                                    cl.addEntity(allEntities[n]);
                                }
                                
                                if (allEntities.length == 1){

                                    cl.addEntity(new Entity(Entity.EntityType.VOID, ""));
                                }

                                p2Linhas.addClause(cl);
                                allProductions[i].removeClause(allClauses[m]);
                            }
                        }

                        Clause c = new Clause(false);
                        c.addEntity(entidadeAux);
                        c.addEntity(new Entity(Entity.EntityType.PRODUCTION, p2Linhas.getName()));

                        allProductions[i].addClause(c);

                        Clause c1 =null;

                        if (allProductions[i].getClauses()[0].getEntities()[0].getType().equals(
                                Entity.EntityType.VOID) || allProductions[i].getClauses().length == 0){


                            allProductions[i].removeClause(allProductions[i].getClauses()[0]);
                            c1 =  new Clause(false);
                            c1.addEntity(new Entity(Entity.EntityType.VOID, ""));
                            allProductions[i].addClause(c1);
                        }

                        if (p2Linhas.getClauses()[0].getEntities()[0].getType().equals(
                                Entity.EntityType.VOID) || p2Linhas.getClauses().length == 0){


                            p2Linhas.removeClause(p2Linhas.getClauses()[0]);
                            c1 = new Clause(false);
                            c1.addEntity(new Entity(Entity.EntityType.VOID, ""));
                            p2Linhas.addClause(c1);
                        }

                        this.addProduction(p2Linhas);

                    }
                }
            }
        }
    }
}






